package org.javanum.number;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.*;

import static org.javanum.number.TestUtils.*;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;

/**
 * Copyright 2010 Scott Fines
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * <p/>
 * Author: Scott Fines
 * Date: Mar 5, 2010
 */
@RunWith(Parameterized.class)
public class ComplexMathTest {
    private final Complex z;
    private final PolarComplex p;
    private final int n;
    private static final double DELTA = Math.pow(1.0,-10);


    public ComplexMathTest(Complex z, PolarComplex p, int n){
        this.z=z;
        this.p=p;
        this.n = n;
    }
    
    @Parameterized.Parameters
	public static Collection<Object[]> getParameters(){

		int randomDataValue = 10;
		List<Object[]> data = new ArrayList<Object[]>(randomDataValue);
		for(int i=randomDataValue;i<=randomDataValue;i+=2){
			for(int j=randomDataValue;j<=randomDataValue;j+=2){
				double scale = 1;
				data.add(new Object[]{
						Complex.valueOf(i*scale, j*scale),
						PolarComplex.valueOf(Math.abs(i)*scale,j*scale),
						i
				});
			}
		}
		return data;
	}

    /*Pythagorean Identity tests*/

    @Test
    public void testPythagoreanIdentityCartesian() throws Exception{
        /*
        Tests that sin(z)^2+cos(z)^2=1 for any input z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);
        Complex sin = ComplexMath.sin(z);
        Complex cos = ComplexMath.cos(z);

        Complex sinSquared = sin.multiply(sin);
        Complex cosSquared = cos.multiply(cos);

        Complex sum = sinSquared.add(cosSquared);
        Complex correct = Complex.valueOf(1,0);

        assertComplexEquality("sin^2(z)+cos^2(z)!=1",correct, sum,DELTA);
    }

    @Test
    public void testPythagoreanIdentityPolar() throws Exception {
        /*
       Tests that sin(z)^2+cos(z)^2=1 for any input z
        */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);
        assumeTrue(Double.compare(p.magnitude, 0)!=0);

        PolarComplex sin = ComplexMath.sin(p);
        PolarComplex cos = ComplexMath.cos(p);

        PolarComplex sinSquared = sin.multiply(sin);
        PolarComplex cosSquared = cos.multiply(cos);

        PolarComplex sum = sinSquared.add(cosSquared);
        PolarComplex correct = PolarComplex.ONE;
        assertComplexEquality("sin^2(z)+cos^2(z)!=1",correct,sum,DELTA);
    }

    @Test
    public void testCotPythagoreanIdentityCartesian() throws Exception {
        /*
        tests that cot^2(z)+1=csc^2(z)
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex cot = ComplexMath.cot(z);
        Complex csc = ComplexMath.csc(z);

        Complex cotS = cot.multiply(cot);
        Complex cscS = csc.multiply(csc);

        Complex lhs = cotS.add(Complex.ONE);

        assertComplexEquality("cot^2(z)+1!=csc^2(z)",cscS,lhs,DELTA);
    }


    @Test
    public void testCotPythagoreanIdentityPolar() throws Exception {
        /*
        tests that cot^2(z)+1=csc^2(z)
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);
        assumeTrue(Double.compare(p.magnitude,0)!=0);

        PolarComplex cot = ComplexMath.cot(p);
        PolarComplex csc = ComplexMath.csc(p);

        PolarComplex cotS = cot.multiply(cot);
        PolarComplex cscS = csc.multiply(csc);

        PolarComplex lhs = cotS.add(PolarComplex.ONE);

        assertComplexEquality("cot^2(z)+1!=csc^2(z)",cscS,lhs,DELTA);
    }

    @Test
    public void testTanPythagoreanIdentityCartesian() throws Exception{
        /*
        Tests that tan^2(z)+1=sec^2(z)
         */
        assumeNotNull(z);
        assumeTrue(!z.equals(Complex.COMPLEX_INFINITY));

        Complex tan = ComplexMath.tan(z);
        Complex sec = ComplexMath.sec(z);

        Complex tanSquared = tan.multiply(tan);
        Complex secSquared = sec.multiply(sec);

        Complex tanSquaredPlusOne = tanSquared.add(Complex.ONE);

        assertComplexEquality("tan^2(z)+1!=sec^2(z)",
                secSquared,tanSquaredPlusOne,DELTA);
    }

    @Test
    public void testTanPythagoreanIdentityPolar() throws Exception {
          /*
        Tests that tan^2(z)+1=sec^2(z)
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);
        assumeTrue(Double.compare(p.magnitude, 0)!=0);
        
        PolarComplex tan = ComplexMath.tan(p);
        PolarComplex sec = ComplexMath.sec(p);

        PolarComplex tanSquared = tan.multiply(tan);
        PolarComplex secSquared = sec.multiply(sec);

        PolarComplex tanSquaredPlusOne = tanSquared.add(PolarComplex.ONE);

        assertComplexEquality("tan^2(z)+1!=sec^2(z)",
                        secSquared,tanSquaredPlusOne,DELTA);
    }

/*------------------------------------------------------------------------*/

    /*Arcsin tests*/
    
    @Test
    public void testArcsinSinInversionCartesian() throws Exception {
        /*
        Tests that sin(Arcsin(z))=z for all z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex arcSin = ComplexMath.arcsin(z);
        Complex sinArcSin = ComplexMath.sin(arcSin);

        assertComplexEquality("sin(arcsin(z))!=z",z,sinArcSin,DELTA);
    }

    /*
     * The following two tests fail because of 
     * domain-range issues. Most notably, it cannot
     * be guaranteed that arcsin(sin(z)) =z for
     * any complex z, since arcsin(sin(pi))=0!=pi.
     * This is because the range of 
     * arcsin in the reals is [-pi/2,pi/2]
     */
//    @Test
//    public void testSinArcsinInversionCartesian() throws Exception {
//        /*
//        Tests that Arcsin(sin(z))=z for all z
//         */
//        assumeNotNull(z);
//        assumeNotComplexInfinity(z);
//
//        Complex sin = ComplexMath.sin(z);
//        Complex arcSinSin = ComplexMath.arcsin(sin);
//
//        assertComplexEquality("arcsin(sin(z))!=z",z,arcSinSin,DELTA);
//    }

//    @Test
//    public void testArcsinSinInversionPolar() throws Exception {
//        /*
//        Tests that arcsin(sin(z))=z for any PolarComplex z
//         */
//        assumeNotNull(p);
//        assumeNotComplexInfinity(p);
//        assumeMagnitudeNotZero(p);
//        
//        PolarComplex sin = ComplexMath.sin(p);
//        PolarComplex arcSinSin = ComplexMath.arcsin(sin);
//        assertComplexEquality("arcsin(sin(z))!=z",p,arcSinSin,DELTA);
//    }

    @Test
    public void testSinArcsinInversionPolar() throws Exception {
        /*
        Tests that sin(arcsin(z))=z for any PolarComplex z
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);
        assumeMagnitudeNotZero(p);
        
        PolarComplex arcSin = ComplexMath.arcsin(p);
        PolarComplex sinArcSin = ComplexMath.sin(arcSin);

        assertComplexEquality("sin(arcsin(z))!=z",p,sinArcSin,DELTA);
    }

/*-----------------------------------------------------------------------*/

    /*Csc tests*/

    @Test
    public void testCscIsOneOverSinCartesian() throws Exception {
        /*
        Tests that csc(z) = 1/sin(z) for any Complex z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex csc = ComplexMath.csc(z);
        Complex sin = ComplexMath.sin(z);
        Complex oneOverSin = Complex.ONE.divide(sin);

        assertComplexEquality("csc(z)!=1/sin(z)",oneOverSin,csc,DELTA);
    }



    @Test
    public void testCscIsOneOverSinPolar() throws Exception {
        /*
        Tests that csc(z)=1/sin(z) for any PolarComplex z
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);

        PolarComplex csc = ComplexMath.csc(p);
        PolarComplex sin = ComplexMath.sin(p);

        PolarComplex oneOverSin = PolarComplex.ONE.divide(sin);

        assertComplexEquality("csc(z)!=1/sin(z)",oneOverSin,csc,DELTA);
    }

/*----------------------------------------------------------------------*/

    /*Arccos tests*/

    @Test
    public void testArccosCosInversionCartesian() throws Exception {
        /*
        Tests that cos(arccos(z))=z for all z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex arcCos = ComplexMath.arccos(z);
        Complex cosArcCos = ComplexMath.cos(arcCos);

        assertComplexEquality("cos(arccos(z))!=z",z,cosArcCos,DELTA);
    }

//    @Test
//    public void testCosArccosInversionCartesian() throws Exception {
//        /*
//        Tests that arccos(cos(z))=z for all z
//         */
//        assumeNotNull(z);
//        assumeNotComplexInfinity(z);
//
//        Complex cos = ComplexMath.cos(z);
//        Complex arcCosCos = ComplexMath.arccos(cos);
//
//        assertComplexEquality("arccos(cos(z))!=z",z,arcCosCos,DELTA);
//
//    }

//    @Test
//    public void testArccosCosInversionPolar() throws Exception {
//        /*
//        Tests that arccos(cos(z))=z for any PolarComplex z
//         */
//        assumeNotNull(p);
//        assumeNotComplexInfinity(p);
//
//        PolarComplex cos = ComplexMath.cos(p);
//        PolarComplex arcCosCos = ComplexMath.arccos(cos);
//
//        assertComplexEquality("arccos(cos(z))!=z",p,arcCosCos,DELTA);
//    }

    @Test
    public void testCosArccosInversionPolar() throws Exception {
        /*
        Tests that cos(arccos(z))=z for any PolarComplex z
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);

        PolarComplex arccos = ComplexMath.arccos(p);
        PolarComplex cosArcCos = ComplexMath.cos(arccos);

        assertComplexEquality("cos(arccos(z))!=z",p,cosArcCos,DELTA);
    }

/*----------------------------------------------------------------------*/

    /* Sec tests*/

    @Test
    public void testSecIsOneOverCosCartesian() throws Exception {
        /*
        Tests that sec(z) = 1/cos(z) for any Complex z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex sec = ComplexMath.sec(z);
        Complex cos = ComplexMath.cos(z);

        Complex oneOverCos = Complex.ONE.divide(cos);

        assertComplexEquality("sec(z)!=1/cos(z)",oneOverCos,sec,DELTA);
    }

    @Test
    public void testSecIsOneOverCosPolar() throws Exception {
        /*
        Tests that sec(z) = 1/cos(z) for any PolarComplex z
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);

        PolarComplex sec = ComplexMath.sec(p);
        PolarComplex cos = ComplexMath.cos(p);

        PolarComplex oneOverCos = PolarComplex.ONE.divide(cos);

        assertComplexEquality("sec(z)!=1/cos(z)",oneOverCos,sec,DELTA);
    }

/*----------------------------------------------------------------------*/

    /*Tan tests*/
    //this is where future tan tests go


/*----------------------------------------------------------------------*/

    /*Arctan tests*/

    @Test
    public void testArctanTanInversionCartesian() throws Exception {
        /*
        Tests that tan(arctan(z))=z for all z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex arcTan = ComplexMath.arctan(z);
        Complex tanArcTan = ComplexMath.tan(arcTan);

        assertComplexEquality("tan(arctan(z))!=z",z,tanArcTan,DELTA);
    }

//    @Test
//    public void testTanArctanInversionCartesian() throws Exception {
//        /*
//        Tests that arctan(tan(z))=z for all z
//         */
//        assumeNotNull(z);
//        assumeNotComplexInfinity(z);
//
//        Complex tan = ComplexMath.tan(z);
//        Complex arcTanTan = ComplexMath.arctan(tan);
//
//        assertComplexEquality("arctan(tan(z))!=z",z,arcTanTan,DELTA);
//    }
//
//    @Test
//    public void testArctanTanInversionPolar() throws Exception {
//        /*
//        Tests that arctan(tan(z))=z for any PolarComplex z
//         */
//        assumeNotNull(p);
//        assumeNotComplexInfinity(p);
//
//        PolarComplex tan = ComplexMath.tan(p);
//        PolarComplex arcTanTan = ComplexMath.arctan(tan);
//
//        assertComplexEquality("arctan(tan(z))!=z",p,arcTanTan,DELTA);
//    }

    @Test
    public void testTanArctanInversionPolar() throws Exception {
       /*
       Tests that tan(arctan(z))=z for any PolarComplex z
        */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);

        PolarComplex arcTan = ComplexMath.arctan(p);
        PolarComplex tanArcTan = ComplexMath.tan(arcTan);

        assertComplexEquality("tan(arctan(z))!=z",p,tanArcTan,DELTA);
    }

/*----------------------------------------------------------------------*/

    /*Cot tests*/
    //this is where future cot tests go


/*----------------------------------------------------------------------*/

    /*Exp tests*/
    //this is where future exp tests go

/*----------------------------------------------------------------------*/

    /*Exp-ln inversion tests*/

    @Test
    public void testExpLnInversionCartesian() throws Exception {
        /*
        tests that exp(ln(z))=z for any Complex z
         */
        assumeNotNull(z);
        assumeNotComplexInfinity(z);

        Complex ln = ComplexMath.ln(z);
        Complex exp = ComplexMath.exp(ln);

        
        assertComplexEquality("exp(ln(z))!=z",z,exp,DELTA);
    }

    @Test
    public void testExpLnInversionPolar() throws Exception {
        /*
        tests that exp(ln(z))=z for any PolarComplex z
         */
        assumeNotNull(p);
        assumeNotComplexInfinity(p);
        assumeMagnitudeNotZero(p);
        PolarComplex ln = ComplexMath.ln(p);
        PolarComplex exp = ComplexMath.exp(ln);

        assertComplexEquality("exp(ln(z))!=z",p,exp,DELTA);
    }

//    @Test
//    public void testLnExpInversionPolar() throws Exception {
//        /*
//        tests that ln(exp(z))=z for any PolarComplex z
//         */
//        assumeNotNull(p);
//        assumeNotComplexInfinity(p);
//
//        PolarComplex exp = ComplexMath.exp(p);
//        PolarComplex ln = ComplexMath.ln(exp);
//        System.out.println(String.format(
//        		"p=%1$s%n" +
//        		"exp(p)=%2$s%n" +
//        		"ln(exp(p))=%3$s",p,exp,ln));
//        assertComplexEquality("ln(exp(z))!=z",p,ln,DELTA);
//    }

/*----------------------------------------------------------------------*/

    /*Ln tests*/
    //This is where future tests of ln go

/*----------------------------------------------------------------------*/
    /*Pow tests*/

    @Test
    public void testPowCartesian() throws Exception {
       /*
        tests that pow(z,n) = z*z*(z n-2 more times)
         */
        assumeNotNull(z,n);
        assumeNotComplexInfinity(z);

        Complex pow = ComplexMath.pow(z,n);
        Complex testPow = testPow(z,n);
        assertComplexEquality("pow(z,n)!=z*z*...*z",testPow,pow,DELTA);
    }

    @Test
    public void testPowPolar() throws Exception {
        /*
        tests that pow(z,n) = z*z*(z n-2 more times)
         */
        assumeNotNull(p,n);
        assumeNotComplexInfinity(p);
        assumeMagnitudeNotZero(p);
        PolarComplex pow = ComplexMath.pow(p,n);
        PolarComplex testPow = testPow(p,n);

        assertComplexEquality("pow(z,n)!=z*z*...*z",testPow,pow,DELTA);
    }

    @Test
    public void testComplexPowCartesian() throws Exception{
    	fail();
    }
    
    @Test
    public void testComplexPowPolar() throws Exception{
    	fail();
    }
    
/*----------------------------------------------------------------------*/
    /*Sqrt tests*/

    @Test
    public void testSqrtCartesian() throws Exception {
        //TODO -sf- implement!
    	fail();
    }

    @Test
    public void testSqrtPolar() throws Exception {
        //TODO -sf- implement!
    	fail();
    }
    
    @Test
    public void testPowSqrtInversionCartesian() throws Exception{
    	/*
    	 * Tests that pow(sqrt(z),2)=z for all Complex z
    	 */
    	assumeNotNull(z);
    	assumeNotComplexInfinity(z);
    	assumeNotNaN(z);
    	
    	Complex sqrt = ComplexMath.sqrt(z);
    	Complex powSqrt = ComplexMath.pow(sqrt, 2);
    	
    	assertComplexEquality("pow(sqrt(z),2)!=z", 
    								z, powSqrt, DELTA);
    }

    @Test
    public void testSqrtPowInversionCartesian() throws Exception{
    	/*
    	 * Tests that sqrt(pow(z,2))=z for all Complex z
    	 */
    	assumeNotNull(z);
    	assumeNotComplexInfinity(z);
    	assumeNotNaN(z);
    	
    	Complex pow = ComplexMath.pow(z, 2);
    	Complex sqrtPow = ComplexMath.sqrt(pow);
    	
    	assertComplexEquality("sqrt(pow(z,2))!=z", 
    			z, sqrtPow, DELTA);
    }

    @Test
    public void testPowSqrtInversionPolar() throws Exception{
    	/*
    	 * Tests that pow(sqrt(z),2)=z for all Complex z
    	 */
    	assumeNotNull(p);
    	assumeNotComplexInfinity(p);
    	assumeNotNaN(p);
    	
    	PolarComplex sqrt = ComplexMath.sqrt(p);
    	PolarComplex powSqrt = ComplexMath.pow(sqrt, 2);
    	
    	assertComplexEquality("pow(sqrt(z),2)!=z", 
    							p, powSqrt, DELTA);
    }

    @Test
    public void testSqrtPowInversionPolar() throws Exception{
    	/*
    	 * Tests that sqrt(pow(z,2))=z for all PolarComplex z.
    	 * 
    	 * Since sqrt(z) has two possible values (z.angle/2 and 
    	 * z.angle/2+-pi), it is possible that sqrt(pow(z,2))
    	 * takes the wrong root so that sqrt(pow(z,2)) !=z.
    	 * This can happen when 2*z.angle > pi or 2*z.angle<-pi.
    	 * In that situation, overflow/underflow occurs to bring
    	 * the angle of pow(z,2) to inside the range (-pi,pi). Then,
    	 * dividing the angle of pow(z,2) may result in the wrong
    	 * branch of the two possible roots.
    	 * 
    	 * Therefore, this test must test that either
    	 * |sqrt(pow(z,2))-z| = 0 or |sqrt(pow(z,2))-z| = pi (angular 
    	 * distance) and that the magnitude of 
    	 * 					sqrt(pow(z,2))= magnitude of z
    	 */
    	assumeNotNull(p);
    	assumeNotComplexInfinity(p);
    	assumeNotNaN(p);
    	
    	PolarComplex pow = ComplexMath.pow(p, 2);
    	PolarComplex sqrtPow = ComplexMath.sqrt(pow);
    	
    	assertEquals(String.format("sqrt(pow(z,2))!=z" +
    			"--magnitudes do not match%n" +
    			"             z=%1$s%n" +
    			"sqrt(pow(z,2))=%2$s",p,sqrtPow),
    			p.magnitude,sqrtPow.magnitude,DELTA);
    	
    	double angularDistance = Math.abs(p.angle-sqrtPow.angle);
    	assertTrue(String.format("sqrt(pow(z,2))!=z" +
    			"--angle is not correct%n" +
    			"             z=%1$s" +
    			"sqrt(pow(z,2))=%2$s",z,sqrtPow),
    			Double.compare(angularDistance,0)==0||
    			Double.compare(angularDistance,Math.PI)==0);
    }
    

/*----------------------------------------------------------------------*/
    /*All roots tests*/

    @Test
    public void testAllRootsPowInversionCartesian() 
    									throws Exception{
        //TODO -sf- implement!
    	assumeNotNull(z);
    	assumeNotComplexInfinity(z);
    	assumeNotNaN(z);
    	assumeTrue(n!=0);
    	
    	Complex[] roots = ComplexMath.allRoots(z,n);
    	for(Complex root:roots){
    		Complex check = ComplexMath.pow(root,n);
    		
    		assertComplexEquality("(z^(1/n))^n!=z", 
    				z, check, DELTA);
    	}
    }

    @Test
    public void testPowAllRootsInversionCartesian() throws Exception{
    	assumeNotNull(z);
    	assumeNotComplexInfinity(z);
    	assumeNotNaN(z);
    	assumeTrue(n!=0);
    	
    	Complex pow = ComplexMath.pow(z, n);
    	
    	Complex[] roots = ComplexMath.allRoots(pow,n);
    	for(Complex root:roots){
    		if(isEqualToAccuracy(z,root,DELTA)){
    			return;
    		}
    	}
    	fail("(z^n)^(1/n)[] does not contain z");
    }
    
    @Test
    public void testAllRootsPowInversionPolar() 
    								throws Exception{
    	assumeNotNull(p);
    	assumeNotComplexInfinity(p);
    	assumeNotNaN(p);
    	assumeTrue(n!=0);
    	
    	PolarComplex[] roots = ComplexMath.allRoots(p, n);
    	for(PolarComplex root:roots){
    		PolarComplex check = ComplexMath.pow(root, n);
    		if(isEqualToAccuracy(p,check,DELTA)){
    			return;
    		}
//    		assertComplexEquality("(z^(1/n))^n!=z", 
//    				p, check, DELTA);
    	}
    	fail("(z^(1/n))^n[] does not contain z");
    }

    @Test
    public void testPowAllRootsInversionPolar() throws Exception{
    	assumeNotNull(p);
    	assumeNotComplexInfinity(p);
    	assumeNotNaN(p);
    	assumeTrue(n!=0);
    	
    	PolarComplex pow = ComplexMath.pow(p, n);
    	
    	PolarComplex[] roots = ComplexMath.allRoots(pow,n);
    	for(PolarComplex root:roots){
    		if(isEqualToAccuracy(p,root,DELTA)){
    			return;
    		}
    	}
    	fail("(z^n)^(1/n)[] does not contain z");
    }

/*----------------------------------------------------------------------*/
    private Complex testPow(Complex z, int n) {
        Complex ret = z;
        for(int i=0;i<n-1;i++){
            ret = ret.multiply(z);
        }
        return ret;  
    }
    
    private PolarComplex testPow(PolarComplex z, int n) {
        PolarComplex ret = z;
        for(int i=0;i<n-1;i++){
            ret = ret.multiply(z);
        }
        return ret;
    }

}
